I. Introduction

This is a comprehensive Exploratory Data Analysis for billions of for-hire vehicle (“FHV”: Uber, Lyft, Via, etc) trips originating in New York City, and we focuse on trip counts and duration in New York competition with tidy R and ggplot2.

The goal of this challenge is to load, process, understand the duration of fhv in NYC based on features: trip location, pick-up and drop-off time. Also, we are interested in the difference betweeen three companies such as market shares, targeted customers, and business strategy. Firstly, we will study and visualize the original data, engineer new features. Then, we add external NYC weather data sets to analyze the impact on the target trip duration. Finally, We compare three companies trips over various time frame on their target trip_duration values.

II. Description of the data source

The data were collected and provided to the NYC Taxi and Limousine Commission (TLC) by technology providers authorized under the Taxicab & Livery Passenger Enhancement Programs (https://www1.nyc.gov/site/tlc/about/tlc-trip-record-data.page).

The For-Hire Vehicle (“FHV”) trip records since 2009 until present including fields capturing the dispatching base license number and the pick-up date, time, and taxi zone location ID. We are focusing on the time period from 2018-01-01 to 2018-12-31, so the data comes in the shape of 200+ million observations, and each row contains one trip infomation.

The base license number is matching with different vehicle companies, so that we will join the base-number file to define the vehicle types, and we only focus on Uber, Lyft, Via at this point.

The NYC Taxi Zones map provided by TLC and published to NYC Open Data(https://data.cityofnewyork.us/Transportation/NYC-Taxi-Zones/d3c5-ddgc). This map shows the NYC taxi zones corresponding to the pick up zomes and drop off zones, or location IDs, included in the FHV trip records. The taxi zones are roughly based on NYC Department of City Planning’s Neighborhood Tabulation Areas (NTAs) and are meant to approximate neighborhoods.

The NYC Weather data is provided by National Centers For Environmental Information (https://www.ncdc.noaa.gov/data-access). NCEI is the world’s largest provider of weather and climate data. Land-based, marine, model, radar, weather balloon, satellite, and paleoclimatic are just a few of the types of datasets available. The weather data we are using is collected from NY Central Park Station (USW00094728) from 2018-01-01 to 2018-12-31, which contains daily weather records such as wind, precipitation, snow and snow depth.

Statistics through June 30, 2018:

Existing problem:

III. Description of data import / cleaning / transformation

3.1 Libraries and Dependencies

library(tibble) # data wrangling
library(reshape2) # data wrangling
library(tidyr) # data wrangling
library(dplyr) # data manipulation
library(purrr) # data manipulation
library(data.table) # data manipulation
library(ggplot2) # visualisation
#library(ggpubr) # visualisation
library(plotly) # visualisation
library(lubridate) # date and time
#library(rgdal) # import GeoJSON

3.2 Data collection

First of all, we write a shell script to download original data from public websites

curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-01.csv >201801.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-02.csv >201802.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-03.csv >201803.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-04.csv >201804.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-05.csv >201805.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-06.csv >201806.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-07.csv >201807.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-08.csv >201808.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-09.csv >201809.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-10.csv >201810.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-11.csv >201811.csv
curl https://s3.amazonaws.com/nyc-tlc/trip+data/fhv_tripdata_2018-12.csv >201812.csv

3.3 Data Import & Cleaning

Then We use data.table:fread function to speed up reading in data for each month, and identify and select the vehicle company as type based on the license number. At the time, we also export subset monthly data into csv file as back up. we bind all monthly data into tibble format to perform our strucuted data. Each row contains trip information such as pick-up, drop-off date, time, location ID.

Due to local memeory issue in R, we process half-year data at one time, and use aggregation technique to compute results and perform entire year analysis. We will explain more detail below.

base = read.csv("./data/orignal/base_number.csv")$x %>% as.character()

load = function(i)
{
  # set file path 
  x = paste("./data/orignal/2018", i, ".csv") %>% gsub(" ", "", .)
  
  # load orginal data
  # identify type
  # remove useless columns
  temp = fread(x) %>%
    filter(Dispatching_base_number %in% c("B02510", "B02800", base)) %>%
    mutate(type = ifelse(Dispatching_base_number == "B02510", "Lyft",
                         ifelse(Dispatching_base_number == "B02800", "Via", "Uber"))) %>%
    select(-SR_Flag, -Dispatching_base_number, -Dispatching_base_num) %>%
    as.tibble()
  
  # export subset monthly data as back-up
  a = paste("./data/", i, ".csv") %>% gsub(" ", "", .)
  write.csv(temp, a)

  return(data)
}

data = c()
for(i in 1:12)
{
  # load original monthly data
  temp = load(i)
  
  # combine data
  data = bind_rows(data, temp)
  
  # optimize memory usage
  remove(temp)
}

3.4 File structure and content

Let’s have an overview of the first 5000 Jan and Dec data. We find the time format is different, so we would like to convert to standard time stamp.

Using summary function to deeply understand the data distribution, and find missing value in DOlocationID

3.5 Data Transformation

Next, we use lubridate:ymd_hms and lubridate:mdy_hms transformat string to standard time stamp variables, and calucate the trip duration in minute by sbustracting drop-off time and pick-up time. Also, we factorize the company types to save memory usage and furture visualization.

# Jan-Nov
Jan %>%
mutate(pick = ymd_hms(Pickup_DateTime), drop = ymd_hms(DropOff_datetime), duration = as.numeric(drop - pick)/60, type = factor(type)) %>%
select(-V1, -Pickup_DateTime, -DropOff_datetime)
# only for Dec
Dec %>%
mutate(pick = mdy_hm(Pickup_DateTime), drop = mdy_hm(DropOff_datetime), duration = as.numeric(drop - pick)/60, type = factor(type)) %>%
select(-V1, -Pickup_DateTime, -DropOff_datetime)

3.6 Data Missing & Outliers

We find there is 91,932 missing value in our dataset. More specific, there are 1,941 Lyft records missing pick-up location, and rest of that are completely bad data records. To conclude accurate analysis, we are going to remove all NA records.

data[which(is.na(data)), ]

Also, due to most companies allow to cancel the order in 2 minutes, we find there are 279,693 records showing the trip duration is less than 2 minutes.

data %>% mutate(duration.large.than.2 = duration > 2 ) %>%
  group_by(duration.large.than.2) %>%
  count

Then, we investage on those pick-up and drop-off location by using heat mapp.

vals = unique(scales::rescale(df$n))
o = order(vals, decreasing = FALSE)
cols = scales::col_numeric("Reds", domain = NULL)(vals)
colz = setNames(data.frame(vals[o], cols[o]), NULL)
plot_ly(data=df, x=~pick_borough,y=~drop_borough,z=~n, colorscale = colz, type = "heatmap")

To conclude, we believe those data are more likely cancelled trip order, so we are going to remove those as well.

3.7 Data Aggregation

By Solving local memory issue in R, since we are interested in the number of trips, and trip duraction, we don’t have to store all data into R. The idea is that we can process half-by-half year data and aggregate into different levels such as hour, weekday, day, and month. Then, we combine aggregated results to make visualization plots, which are much smaller.

data %>%
    mutate(hour = hour(pick), wday = weekdays(pick), type) %>%
    group_by(hour, wday, type) %>%
    count
data %>%
    mutate(month = month.abb[month(pick)]) %>%
    group_by(month, type) %>%
    summarise(d.med = median(duration))

V. Results

y = read.csv("./1 wday duration.csv") %>% mutate(wday = as.character(wday)) %>% as.tibble()

z = read.csv("./2 wday duration.csv") %>% mutate(wday = as.character(wday)) %>% as.tibble()

inner_join(y, z, by = "wday") %>% mutate(d.med = (d.total.x + d.total.y)/(n.x + n.y)) %>% select(wday, d.med) %>% write.csv(., "wday duration.csv")

bind_rows(y, z) %>% select(-X) %>% write.csv("./day duration.csv")
read.csv("hourly duration.csv") %>%
plot_ly(., x = ~ hour, y = ~ d.med, type ="scatter", mode = 'lines+markers',line = list(color="#2E86C1", width = 4), marker = list(size = 8, color = 'rgba(255, 182, 193, .9)', line = list(color = 'rgba(152, 0, 0, .8)', width = 2,simplyfy = F)), text = ~ paste("Hour: ", hour, '<br>Average duration:', round(d.med,2)), main="average duration vs hour") %>%
  layout(title = 'Hourly Median Trip Duration(min)', yaxis = list(title = 'Duration', zeroline = FALSE),
         xaxis = list(title = 'Hour',zeroline = FALSE))
'scatter' objects don't have these attributes: 'main'
Valid attributes include:
'type', 'visible', 'showlegend', 'legendgroup', 'opacity', 'name', 'uid', 'ids', 'customdata', 'selectedpoints', 'hoverinfo', 'hoverlabel', 'stream', 'transforms', 'uirevision', 'x', 'x0', 'dx', 'y', 'y0', 'dy', 'stackgroup', 'orientation', 'groupnorm', 'stackgaps', 'text', 'hovertext', 'mode', 'hoveron', 'hovertemplate', 'line', 'connectgaps', 'cliponaxis', 'fill', 'fillcolor', 'marker', 'selected', 'unselected', 'textposition', 'textfont', 'r', 't', 'error_x', 'error_y', 'xcalendar', 'ycalendar', 'xaxis', 'yaxis', 'idssrc', 'customdatasrc', 'hoverinfosrc', 'xsrc', 'ysrc', 'textsrc', 'hovertextsrc', 'hovertemplatesrc', 'textpositionsrc', 'rsrc', 'tsrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
'scatter' objects don't have these attributes: 'main'
Valid attributes include:
'type', 'visible', 'showlegend', 'legendgroup', 'opacity', 'name', 'uid', 'ids', 'customdata', 'selectedpoints', 'hoverinfo', 'hoverlabel', 'stream', 'transforms', 'uirevision', 'x', 'x0', 'dx', 'y', 'y0', 'dy', 'stackgroup', 'orientation', 'groupnorm', 'stackgaps', 'text', 'hovertext', 'mode', 'hoveron', 'hovertemplate', 'line', 'connectgaps', 'cliponaxis', 'fill', 'fillcolor', 'marker', 'selected', 'unselected', 'textposition', 'textfont', 'r', 't', 'error_x', 'error_y', 'xcalendar', 'ycalendar', 'xaxis', 'yaxis', 'idssrc', 'customdatasrc', 'hoverinfosrc', 'xsrc', 'ysrc', 'textsrc', 'hovertextsrc', 'hovertemplatesrc', 'textpositionsrc', 'rsrc', 'tsrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isGraticule', '_bbox'
lvl = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
read.csv("wday duration.csv") %>% mutate(wday = ordered(wday, levels = lvl)) %>% arrange(wday) %>%
  plot_ly(., x = ~ wday, y = ~ d.med, type ="scatter", mode = 'lines+markers', line = list(width=4,simplyfy = F, color='rgb(114, 186, 59)'), marker = list(size = 8, color = '#8E44AD', line = list(color = '#3498DB', width = 2)), text = ~paste("Weekday: ", wday, '<br>Median duration:', round(d.med,2))) %>%
  layout(title = 'Weekday Median Trip Duration(min) for FHV types',
         yaxis = list(title = 'Duration',zeroline = FALSE),
         xaxis = list(title = 'Weekday',zeroline = FALSE))
LS0tDQp0aXRsZTogIkZpbmFsIFByb2plY3QiDQphdXRob3I6ICJKaWUgTGkiDQpkYXRlOiAiQXByaWwgMjUsIDIwMTkiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KIyMgSS4gSW50cm9kdWN0aW9uDQpUaGlzIGlzIGEgY29tcHJlaGVuc2l2ZSBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIGZvciBiaWxsaW9ucyBvZiBmb3ItaGlyZSB2ZWhpY2xlICgiRkhWIjogVWJlciwgTHlmdCwgVmlhLCBldGMpIHRyaXBzIG9yaWdpbmF0aW5nIGluIE5ldyBZb3JrIENpdHksIGFuZCB3ZSBmb2N1c2Ugb24gdHJpcCBjb3VudHMgYW5kIGR1cmF0aW9uIGluIE5ldyBZb3JrIGNvbXBldGl0aW9uIHdpdGggdGlkeSBSIGFuZCBnZ3Bsb3QyLg0KDQpUaGUgZ29hbCBvZiB0aGlzIGNoYWxsZW5nZSBpcyB0byBsb2FkLCBwcm9jZXNzLCB1bmRlcnN0YW5kIHRoZSBkdXJhdGlvbiBvZiBmaHYgaW4gTllDIGJhc2VkIG9uIGZlYXR1cmVzOiB0cmlwIGxvY2F0aW9uLCBwaWNrLXVwIGFuZCBkcm9wLW9mZiB0aW1lLiBBbHNvLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVlbiB0aHJlZSBjb21wYW5pZXMgc3VjaCBhcyBtYXJrZXQgc2hhcmVzLCB0YXJnZXRlZCBjdXN0b21lcnMsIGFuZCBidXNpbmVzcyBzdHJhdGVneS4gRmlyc3RseSwgd2Ugd2lsbCBzdHVkeSBhbmQgdmlzdWFsaXplIHRoZSBvcmlnaW5hbCBkYXRhLCBlbmdpbmVlciBuZXcgZmVhdHVyZXMuIFRoZW4sIHdlIGFkZCBleHRlcm5hbCBOWUMgd2VhdGhlciBkYXRhIHNldHMgdG8gYW5hbHl6ZSB0aGUgaW1wYWN0IG9uIHRoZSB0YXJnZXQgdHJpcCBkdXJhdGlvbi4gRmluYWxseSwgV2UgY29tcGFyZSB0aHJlZSBjb21wYW5pZXMgdHJpcHMgb3ZlciB2YXJpb3VzIHRpbWUgZnJhbWUgb24gdGhlaXIgdGFyZ2V0IHRyaXBfZHVyYXRpb24gdmFsdWVzLg0KDQojIyBJSS4gRGVzY3JpcHRpb24gb2YgdGhlIGRhdGEgc291cmNlDQpUaGUgZGF0YSB3ZXJlIGNvbGxlY3RlZCBhbmQgcHJvdmlkZWQgdG8gdGhlIE5ZQyBUYXhpIGFuZCBMaW1vdXNpbmUgQ29tbWlzc2lvbiAoVExDKSBieSB0ZWNobm9sb2d5IHByb3ZpZGVycyBhdXRob3JpemVkIHVuZGVyIHRoZSBUYXhpY2FiICYgTGl2ZXJ5IFBhc3NlbmdlciBFbmhhbmNlbWVudCBQcm9ncmFtcyAoaHR0cHM6Ly93d3cxLm55Yy5nb3Yvc2l0ZS90bGMvYWJvdXQvdGxjLXRyaXAtcmVjb3JkLWRhdGEucGFnZSkuDQoNClRoZSBGb3ItSGlyZSBWZWhpY2xlICgiRkhWIikgdHJpcCByZWNvcmRzIHNpbmNlIDIwMDkgdW50aWwgcHJlc2VudCBpbmNsdWRpbmcgZmllbGRzIGNhcHR1cmluZyB0aGUgZGlzcGF0Y2hpbmcgYmFzZSBsaWNlbnNlIG51bWJlciBhbmQgdGhlIHBpY2stdXAgZGF0ZSwgdGltZSwgYW5kIHRheGkgem9uZSBsb2NhdGlvbiBJRC4gV2UgYXJlIGZvY3VzaW5nIG9uIHRoZSB0aW1lIHBlcmlvZCBmcm9tICoqMjAxOC0wMS0wMSoqIHRvICoqMjAxOC0xMi0zMSoqLCBzbyB0aGUgZGF0YSBjb21lcyBpbiB0aGUgc2hhcGUgb2YgMjAwKyBtaWxsaW9uIG9ic2VydmF0aW9ucywgYW5kIGVhY2ggcm93IGNvbnRhaW5zIG9uZSB0cmlwIGluZm9tYXRpb24uDQoNClRoZSBiYXNlIGxpY2Vuc2UgbnVtYmVyIGlzIG1hdGNoaW5nIHdpdGggZGlmZmVyZW50IHZlaGljbGUgY29tcGFuaWVzLCBzbyB0aGF0IHdlIHdpbGwgam9pbiB0aGUgYGJhc2UtbnVtYmVyYCBmaWxlIHRvIGRlZmluZSB0aGUgdmVoaWNsZSB0eXBlcywgYW5kIHdlIG9ubHkgZm9jdXMgb24gVWJlciwgTHlmdCwgVmlhIGF0IHRoaXMgcG9pbnQuDQoNClRoZSBOWUMgVGF4aSBab25lcyBtYXAgcHJvdmlkZWQgYnkgVExDIGFuZCBwdWJsaXNoZWQgdG8gTllDIE9wZW4gRGF0YShodHRwczovL2RhdGEuY2l0eW9mbmV3eW9yay51cy9UcmFuc3BvcnRhdGlvbi9OWUMtVGF4aS1ab25lcy9kM2M1LWRkZ2MpLiBUaGlzIG1hcCBzaG93cyB0aGUgTllDIHRheGkgem9uZXMgY29ycmVzcG9uZGluZyB0byB0aGUgcGljayB1cCB6b21lcyBhbmQgZHJvcCBvZmYgem9uZXMsIG9yIGxvY2F0aW9uIElEcywgaW5jbHVkZWQgaW4gdGhlIEZIViB0cmlwIHJlY29yZHMuIFRoZSB0YXhpIHpvbmVzIGFyZSByb3VnaGx5IGJhc2VkIG9uIE5ZQyBEZXBhcnRtZW50IG9mIENpdHkgUGxhbm5pbmcncyBOZWlnaGJvcmhvb2QgVGFidWxhdGlvbiBBcmVhcyAoTlRBcykgYW5kIGFyZSBtZWFudCB0byBhcHByb3hpbWF0ZSBuZWlnaGJvcmhvb2RzLg0KDQpUaGUgTllDIFdlYXRoZXIgZGF0YSBpcyBwcm92aWRlZCBieSBOYXRpb25hbCBDZW50ZXJzIEZvciBFbnZpcm9ubWVudGFsIEluZm9ybWF0aW9uIChodHRwczovL3d3dy5uY2RjLm5vYWEuZ292L2RhdGEtYWNjZXNzKS4gTkNFSSBpcyB0aGUgd29ybGQncyBsYXJnZXN0IHByb3ZpZGVyIG9mIHdlYXRoZXIgYW5kIGNsaW1hdGUgZGF0YS4gTGFuZC1iYXNlZCwgbWFyaW5lLCBtb2RlbCwgcmFkYXIsIHdlYXRoZXIgYmFsbG9vbiwgc2F0ZWxsaXRlLCBhbmQgcGFsZW9jbGltYXRpYyBhcmUganVzdCBhIGZldyBvZiB0aGUgdHlwZXMgb2YgZGF0YXNldHMgYXZhaWxhYmxlLiBUaGUgd2VhdGhlciBkYXRhIHdlIGFyZSB1c2luZyBpcyBjb2xsZWN0ZWQgZnJvbSBOWSBDZW50cmFsIFBhcmsgU3RhdGlvbiAoVVNXMDAwOTQ3MjgpIGZyb20gKioyMDE4LTAxLTAxKiogdG8gKioyMDE4LTEyLTMxKiosIHdoaWNoIGNvbnRhaW5zIGRhaWx5IHdlYXRoZXIgcmVjb3JkcyBzdWNoIGFzIHdpbmQsIHByZWNpcGl0YXRpb24sIHNub3cgYW5kIHNub3cgZGVwdGguDQoNClN0YXRpc3RpY3MgdGhyb3VnaCBKdW5lIDMwLCAyMDE4Og0KDQoqIDE3LjIgR0Igb2YgcmF3IGRhdGENCiogMjAwKyBtaWxsaW9uIGZvci1oaXJlIHZlaGljbGUgdG90YWwgdHJpcHMNCiogMzY1IGRhaWx5IHdlYXRoZXIgcmVjb3Jkcw0KDQpFeGlzdGluZyBwcm9ibGVtOg0KDQoqIFIgcmVhZHMgZW50aXJlIGRhdGEgc2V0IGludG8gUkFNIGFsbCBhdCBvbmNlLiBUb3RhbCAxNy4yIEdCIG9mIHJhdyBkYXRhIHdvdWxkIG5vdCBmaXQgaW4gbG9jYWwgbWVtb3J5IGF0IG9uY2UuDQoqIFIgT2JqZWN0cyBsaXZlIGluIG1lbW9yeSBlbnRpcmVseSwgd2hpY2ggY2F1c2Ugc2xvd25lc3MgZm9yIGRhdGEgYW5hbHlzaXMuDQoqIFRoZSBUTEMgcHVibGlzaGVzIGJhc2UgdHJpcCByZWNvcmQgZGF0YSBhcyBzdWJtaXR0ZWQgYnkgdGhlIGJhc2VzLCBhbmQgd2UgY2Fubm90IGd1YXJhbnRlZSBvciBjb25maXJtIHRoZWlyIGFjY3VyYWN5IG9yIGNvbXBsZXRlbmVzcy4NCg0KDQojIyBJSUkuIERlc2NyaXB0aW9uIG9mIGRhdGEgaW1wb3J0IC8gY2xlYW5pbmcgLyB0cmFuc2Zvcm1hdGlvbg0KIyMjIDMuMSBMaWJyYXJpZXMgYW5kIERlcGVuZGVuY2llcw0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQ0KbGlicmFyeSh0aWJibGUpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkocmVzaGFwZTIpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkodGlkeXIpICMgZGF0YSB3cmFuZ2xpbmcNCmxpYnJhcnkoZHBseXIpICMgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkocHVycnIpICMgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkoZGF0YS50YWJsZSkgIyBkYXRhIG1hbmlwdWxhdGlvbg0KbGlicmFyeShnZ3Bsb3QyKSAjIHZpc3VhbGlzYXRpb24NCiNsaWJyYXJ5KGdncHVicikgIyB2aXN1YWxpc2F0aW9uDQpsaWJyYXJ5KHBsb3RseSkgIyB2aXN1YWxpc2F0aW9uDQpsaWJyYXJ5KGx1YnJpZGF0ZSkgIyBkYXRlIGFuZCB0aW1lDQojbGlicmFyeShyZ2RhbCkgIyBpbXBvcnQgR2VvSlNPTg0KYGBgDQoNCmBgYHtyLCBlY2hvPUZ9DQpzZXR3ZCgiRTovTllDIFRheGkgUHJvamVjdC8iKQ0KYGBgDQoNCiMjIyAzLjIgRGF0YSBjb2xsZWN0aW9uDQpGaXJzdCBvZiBhbGwsIHdlIHdyaXRlIGEgYHNoZWxsYCBzY3JpcHQgdG8gZG93bmxvYWQgb3JpZ2luYWwgZGF0YSBmcm9tIHB1YmxpYyB3ZWJzaXRlcw0KYGBge3IsIGV2YWw9Rn0NCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTAxLmNzdiA+MjAxODAxLmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDIuY3N2ID4yMDE4MDIuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wMy5jc3YgPjIwMTgwMy5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTA0LmNzdiA+MjAxODA0LmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDUuY3N2ID4yMDE4MDUuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wNi5jc3YgPjIwMTgwNi5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTA3LmNzdiA+MjAxODA3LmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMDguY3N2ID4yMDE4MDguY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0wOS5jc3YgPjIwMTgwOS5jc3YNCmN1cmwgaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL255Yy10bGMvdHJpcCtkYXRhL2Zodl90cmlwZGF0YV8yMDE4LTEwLmNzdiA+MjAxODEwLmNzdg0KY3VybCBodHRwczovL3MzLmFtYXpvbmF3cy5jb20vbnljLXRsYy90cmlwK2RhdGEvZmh2X3RyaXBkYXRhXzIwMTgtMTEuY3N2ID4yMDE4MTEuY3N2DQpjdXJsIGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9ueWMtdGxjL3RyaXArZGF0YS9maHZfdHJpcGRhdGFfMjAxOC0xMi5jc3YgPjIwMTgxMi5jc3YNCmBgYA0KDQojIyMgMy4zIERhdGEgSW1wb3J0ICYgQ2xlYW5pbmcNClRoZW4gV2UgdXNlIGBkYXRhLnRhYmxlOmZyZWFkYCBmdW5jdGlvbiB0byBzcGVlZCB1cCByZWFkaW5nIGluIGRhdGEgZm9yIGVhY2ggbW9udGgsIGFuZCBpZGVudGlmeSBhbmQgc2VsZWN0IHRoZSB2ZWhpY2xlIGNvbXBhbnkgYXMgdHlwZSBiYXNlZCBvbiB0aGUgbGljZW5zZSBudW1iZXIuIEF0IHRoZSB0aW1lLCB3ZSBhbHNvIGV4cG9ydCBzdWJzZXQgbW9udGhseSBkYXRhIGludG8gYGNzdmAgZmlsZSBhcyBiYWNrIHVwLiB3ZSBiaW5kIGFsbCBtb250aGx5IGRhdGEgaW50byBgdGliYmxlYCBmb3JtYXQgdG8gcGVyZm9ybSBvdXIgc3RydWN1dGVkIGRhdGEuIEVhY2ggcm93IGNvbnRhaW5zIHRyaXAgaW5mb3JtYXRpb24gc3VjaCBhcyBwaWNrLXVwLCBkcm9wLW9mZiBkYXRlLCB0aW1lLCBsb2NhdGlvbiBJRC4NCg0KRHVlIHRvIGxvY2FsIG1lbWVvcnkgaXNzdWUgaW4gUiwgd2UgcHJvY2VzcyBoYWxmLXllYXIgZGF0YSBhdCBvbmUgdGltZSwgYW5kIHVzZSAqKmFnZ3JlZ2F0aW9uKiogdGVjaG5pcXVlIHRvIGNvbXB1dGUgcmVzdWx0cyBhbmQgcGVyZm9ybSBlbnRpcmUgeWVhciBhbmFseXNpcy4gV2Ugd2lsbCBleHBsYWluIG1vcmUgZGV0YWlsIGJlbG93Lg0KDQpgYGB7ciwgZXZhbD1GfQ0KYmFzZSA9IHJlYWQuY3N2KCIuL2RhdGEvb3JpZ25hbC9iYXNlX251bWJlci5jc3YiKSR4ICU+JSBhcy5jaGFyYWN0ZXIoKQ0KDQpsb2FkID0gZnVuY3Rpb24oaSkNCnsNCiAgIyBzZXQgZmlsZSBwYXRoIA0KICB4ID0gcGFzdGUoIi4vZGF0YS9vcmlnbmFsLzIwMTgiLCBpLCAiLmNzdiIpICU+JSBnc3ViKCIgIiwgIiIsIC4pDQogIA0KICAjIGxvYWQgb3JnaW5hbCBkYXRhDQogICMgaWRlbnRpZnkgdHlwZQ0KICAjIHJlbW92ZSB1c2VsZXNzIGNvbHVtbnMNCiAgdGVtcCA9IGZyZWFkKHgpICU+JQ0KICAgIGZpbHRlcihEaXNwYXRjaGluZ19iYXNlX251bWJlciAlaW4lIGMoIkIwMjUxMCIsICJCMDI4MDAiLCBiYXNlKSkgJT4lDQogICAgbXV0YXRlKHR5cGUgPSBpZmVsc2UoRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIgPT0gIkIwMjUxMCIsICJMeWZ0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIgPT0gIkIwMjgwMCIsICJWaWEiLCAiVWJlciIpKSkgJT4lDQogICAgc2VsZWN0KC1TUl9GbGFnLCAtRGlzcGF0Y2hpbmdfYmFzZV9udW1iZXIsIC1EaXNwYXRjaGluZ19iYXNlX251bSkgJT4lDQogICAgYXMudGliYmxlKCkNCiAgDQogICMgZXhwb3J0IHN1YnNldCBtb250aGx5IGRhdGEgYXMgYmFjay11cA0KICBhID0gcGFzdGUoIi4vZGF0YS8iLCBpLCAiLmNzdiIpICU+JSBnc3ViKCIgIiwgIiIsIC4pDQogIHdyaXRlLmNzdih0ZW1wLCBhKQ0KDQogIHJldHVybihkYXRhKQ0KfQ0KDQpkYXRhID0gYygpDQpmb3IoaSBpbiAxOjEyKQ0Kew0KICAjIGxvYWQgb3JpZ2luYWwgbW9udGhseSBkYXRhDQogIHRlbXAgPSBsb2FkKGkpDQogIA0KICAjIGNvbWJpbmUgZGF0YQ0KICBkYXRhID0gYmluZF9yb3dzKGRhdGEsIHRlbXApDQogIA0KICAjIG9wdGltaXplIG1lbW9yeSB1c2FnZQ0KICByZW1vdmUodGVtcCkNCn0NCmBgYA0KDQojIyMgMy40IEZpbGUgc3RydWN0dXJlIGFuZCBjb250ZW50DQpMZXQncyBoYXZlIGFuIG92ZXJ2aWV3IG9mIHRoZSBmaXJzdCA1MDAwIGBKYW5gIGFuZCBgRGVjYCBkYXRhLiBXZSBmaW5kIHRoZSB0aW1lIGZvcm1hdCBpcyBkaWZmZXJlbnQsIHNvIHdlIHdvdWxkIGxpa2UgdG8gY29udmVydCB0byBzdGFuZGFyZCB0aW1lIHN0YW1wLg0KDQpgYGB7ciwgZWNobz1GfQ0KSmFuID0gcmVhZC5jc3YoIi4vZGF0YS8wMS5jc3YiKSAlPiUgc2VsZWN0KC1YKQ0KRGVjID0gcmVhZC5jc3YoIi4vZGF0YS8wMTIuY3N2IikgJT4lIHNlbGVjdCgtWCkNCg0KSmFuDQpEZWMNCmBgYA0KDQpVc2luZyBgc3VtbWFyeWAgZnVuY3Rpb24gdG8gZGVlcGx5IHVuZGVyc3RhbmQgdGhlIGRhdGEgZGlzdHJpYnV0aW9uLCBhbmQgZmluZCBtaXNzaW5nIHZhbHVlIGluIGBET2xvY2F0aW9uSURgDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoIi4vc3VtbWFyeSBKYW4uY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpIA0KcmVhZC5jc3YoIi4vc3VtbWFyeSBEZWMuY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KIyMjIDMuNSBEYXRhIFRyYW5zZm9ybWF0aW9uDQpOZXh0LCB3ZSB1c2UgYGx1YnJpZGF0ZTp5bWRfaG1zYCBhbmQgYGx1YnJpZGF0ZTptZHlfaG1zYCB0cmFuc2Zvcm1hdCBzdHJpbmcgdG8gc3RhbmRhcmQgdGltZSBzdGFtcCB2YXJpYWJsZXMsIGFuZCBjYWx1Y2F0ZSB0aGUgdHJpcCBkdXJhdGlvbiBpbiAqKm1pbnV0ZSoqIGJ5IHNidXN0cmFjdGluZyBkcm9wLW9mZiB0aW1lIGFuZCBwaWNrLXVwIHRpbWUuIEFsc28sIHdlIGZhY3Rvcml6ZSB0aGUgY29tcGFueSB0eXBlcyB0byBzYXZlIG1lbW9yeSB1c2FnZSBhbmQgZnVydHVyZSB2aXN1YWxpemF0aW9uLg0KDQpgYGB7cn0NCiMgSmFuLU5vdg0KSmFuICU+JQ0KbXV0YXRlKHBpY2sgPSB5bWRfaG1zKFBpY2t1cF9EYXRlVGltZSksIGRyb3AgPSB5bWRfaG1zKERyb3BPZmZfZGF0ZXRpbWUpLCBkdXJhdGlvbiA9IGFzLm51bWVyaWMoZHJvcCAtIHBpY2spLzYwLCB0eXBlID0gZmFjdG9yKHR5cGUpKSAlPiUNCnNlbGVjdCgtVjEsIC1QaWNrdXBfRGF0ZVRpbWUsIC1Ecm9wT2ZmX2RhdGV0aW1lKQ0KDQojIG9ubHkgZm9yIERlYw0KRGVjICU+JQ0KbXV0YXRlKHBpY2sgPSBtZHlfaG0oUGlja3VwX0RhdGVUaW1lKSwgZHJvcCA9IG1keV9obShEcm9wT2ZmX2RhdGV0aW1lKSwgZHVyYXRpb24gPSBhcy5udW1lcmljKGRyb3AgLSBwaWNrKS82MCwgdHlwZSA9IGZhY3Rvcih0eXBlKSkgJT4lDQpzZWxlY3QoLVYxLCAtUGlja3VwX0RhdGVUaW1lLCAtRHJvcE9mZl9kYXRldGltZSkNCmBgYA0KDQojIyMgMy42IERhdGEgTWlzc2luZyAmIE91dGxpZXJzDQoNCldlIGZpbmQgdGhlcmUgaXMgOTEsOTMyIG1pc3NpbmcgdmFsdWUgaW4gb3VyIGRhdGFzZXQuIE1vcmUgc3BlY2lmaWMsIHRoZXJlIGFyZSAxLDk0MSBMeWZ0IHJlY29yZHMgbWlzc2luZyBwaWNrLXVwIGxvY2F0aW9uLCBhbmQgcmVzdCBvZiB0aGF0IGFyZSBjb21wbGV0ZWx5IGJhZCBkYXRhIHJlY29yZHMuIFRvIGNvbmNsdWRlIGFjY3VyYXRlIGFuYWx5c2lzLCB3ZSBhcmUgZ29pbmcgdG8gcmVtb3ZlIGFsbCBgTkFgIHJlY29yZHMuDQpgYGB7ciwgZXZhbD1GfQ0KZGF0YVt3aGljaChpcy5uYShkYXRhKSksIF0NCmBgYA0KDQpgYGB7ciwgZWNobz1GfQ0KeCA9IHJlYWQuY3N2KCIuL21pc3NpbmcuY3N2IikgJT4lIGFzLnRpYmJsZSgpICU+JSBtdXRhdGUocGljayA9IG1keV9obShwaWNrKSwgZHJvcCA9IG1keV9obShkcm9wKSkNCngNCnRhaWwoeCkNCmBgYA0KDQpBbHNvLCBkdWUgdG8gbW9zdCBjb21wYW5pZXMgYWxsb3cgdG8gY2FuY2VsIHRoZSBvcmRlciBpbiAyIG1pbnV0ZXMsIHdlIGZpbmQgdGhlcmUgYXJlIDI3OSw2OTMgcmVjb3JkcyBzaG93aW5nIHRoZSB0cmlwIGR1cmF0aW9uIGlzIGxlc3MgdGhhbiAyIG1pbnV0ZXMuDQoNCmBgYHtyLCBldmFsPUZ9DQpkYXRhICU+JSBtdXRhdGUoZHVyYXRpb24ubGFyZ2UudGhhbi4yID0gZHVyYXRpb24gPiAyICkgJT4lDQogIGdyb3VwX2J5KGR1cmF0aW9uLmxhcmdlLnRoYW4uMikgJT4lDQogIGNvdW50DQpgYGANCg0KYGBge3IsIGVjaG89Rn0NCnJlYWQuY3N2KCIuLzJtaW5fY291bnRzLmNzdiIpICU+JSBhcy50aWJibGUoKQ0KYGBgDQoNCg0KYGBge3IsIGVjaG89Rn0NCmRmID0gcmVhZC5jc3YoIi4vZGF0YS9sZXNzX3RoYW5fMi5jc3YiKQ0KcGljayA9IHJlYWQuY3N2KCIuL2RpY3Rpb25hcnlfcGlja3VwLmNzdiIpICU+JSBhcy50aWJibGUoKQ0KZHJvcCA9IHJlYWQuY3N2KCIuL2RpY3Rpb25hcnlfZHJvcG9mZi5jc3YiKSAlPiUgYXMudGliYmxlKCkNCg0KbWF0Y2hfem9uZSA9IGZ1bmN0aW9uKGRmLGRpY3Rpb25hcnlfcGlja3VwLGRpY3Rpb25hcnlfZHJvcG9mZil7DQoNCiAgbWF0Y2hlZC5kZiA9IGRmICU+JSANCiAgIGxlZnRfam9pbihkaWN0aW9uYXJ5X3BpY2t1cCxieT0iUFVsb2NhdGlvbklEIikgJT4lIA0KICAgbXV0YXRlKHBpY2tfYm9yb3VnaCA9IGJvcm91Z2gpICU+JSANCiAgIHNlbGVjdCgtYm9yb3VnaCklPiUNCiAgIGxlZnRfam9pbihkaWN0aW9uYXJ5X2Ryb3BvZmYsYnk9IkRPbG9jYXRpb25JRCIpICU+JSANCiAgIG11dGF0ZShkcm9wX2Jvcm91Z2ggPSBib3JvdWdoKSAlPiUgIA0KICBzZWxlY3QoLWJvcm91Z2gsLXR5cGUsLXBpY2ssLWRyb3ApICU+JSANCiAgIGZpbHRlcighaXMubmEoZHJvcF9ib3JvdWdoKSkgJT4lIA0KICAgZmlsdGVyKCFpcy5uYShwaWNrX2Jvcm91Z2gpKSAlPiUgDQogIGdyb3VwX2J5KHBpY2tfYm9yb3VnaCxkcm9wX2Jvcm91Z2gpIA0KDQogIHJldHVybihtYXRjaGVkLmRmKQ0KfQ0KDQpkZiA9IG1hdGNoX3pvbmUoZGYscGljayxkcm9wKSAlPiUgY291bnQoKQ0KYGBgDQoNClRoZW4sIHdlIGludmVzdGFnZSBvbiB0aG9zZSBwaWNrLXVwIGFuZCBkcm9wLW9mZiBsb2NhdGlvbiBieSB1c2luZyAqKmhlYXQgbWFwcCoqLiANCmBgYHtyfQ0KdmFscyA9IHVuaXF1ZShzY2FsZXM6OnJlc2NhbGUoZGYkbikpDQpvID0gb3JkZXIodmFscywgZGVjcmVhc2luZyA9IEZBTFNFKQ0KY29scyA9IHNjYWxlczo6Y29sX251bWVyaWMoIlJlZHMiLCBkb21haW4gPSBOVUxMKSh2YWxzKQ0KY29seiA9IHNldE5hbWVzKGRhdGEuZnJhbWUodmFsc1tvXSwgY29sc1tvXSksIE5VTEwpDQpwbG90X2x5KGRhdGE9ZGYsIHg9fnBpY2tfYm9yb3VnaCx5PX5kcm9wX2Jvcm91Z2gsej1+biwgY29sb3JzY2FsZSA9IGNvbHosIHR5cGUgPSAiaGVhdG1hcCIpDQpgYGANCg0KVG8gY29uY2x1ZGUsIHdlIGJlbGlldmUgdGhvc2UgZGF0YSBhcmUgbW9yZSBsaWtlbHkgY2FuY2VsbGVkIHRyaXAgb3JkZXIsIHNvIHdlIGFyZSBnb2luZyB0byByZW1vdmUgdGhvc2UgYXMgd2VsbC4NCg0KDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoIi4vMm1pbl9jb3VudHMuY3N2IikgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KIyMjIDMuNyBEYXRhIEFnZ3JlZ2F0aW9uDQpCeSBTb2x2aW5nIGxvY2FsIG1lbW9yeSBpc3N1ZSBpbiBSLCBzaW5jZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgbnVtYmVyIG9mIHRyaXBzLCBhbmQgdHJpcCBkdXJhY3Rpb24sIHdlIGRvbid0IGhhdmUgdG8gc3RvcmUgYWxsIGRhdGEgaW50byBSLiBUaGUgaWRlYSBpcyB0aGF0IHdlIGNhbiBwcm9jZXNzIGhhbGYtYnktaGFsZiB5ZWFyIGRhdGEgYW5kIGFnZ3JlZ2F0ZSBpbnRvIGRpZmZlcmVudCBsZXZlbHMgc3VjaCBhcyBob3VyLCB3ZWVrZGF5LCBkYXksIGFuZCBtb250aC4gVGhlbiwgd2UgY29tYmluZSBhZ2dyZWdhdGVkIHJlc3VsdHMgdG8gbWFrZSB2aXN1YWxpemF0aW9uIHBsb3RzLCB3aGljaCBhcmUgbXVjaCBzbWFsbGVyLg0KDQpgYGB7ciwgZXZhbD1GfQ0KZGF0YSAlPiUNCiAgICBtdXRhdGUoaG91ciA9IGhvdXIocGljayksIHdkYXkgPSB3ZWVrZGF5cyhwaWNrKSwgdHlwZSkgJT4lDQogICAgZ3JvdXBfYnkoaG91ciwgd2RheSwgdHlwZSkgJT4lDQogICAgY291bnQNCmBgYA0KDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoImNvdW50cyBieSBoX3drX3R5cGUuY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KYGBge3IsIGV2YWw9Rn0NCmRhdGEgJT4lDQogICAgbXV0YXRlKG1vbnRoID0gbW9udGguYWJiW21vbnRoKHBpY2spXSkgJT4lDQogICAgZ3JvdXBfYnkobW9udGgsIHR5cGUpICU+JQ0KICAgIHN1bW1hcmlzZShkLm1lZCA9IG1lZGlhbihkdXJhdGlvbikpDQpgYGANCg0KDQpgYGB7ciwgZWNobz1GfQ0KcmVhZC5jc3YoIm1vbnRoIHR5cGUgZHVyYXRpb24uY3N2IikgJT4lIHNlbGVjdCgtWCkgJT4lIGFzLnRpYmJsZSgpDQpgYGANCg0KIyMgVi4gUmVzdWx0cw0KYGBge3IsIGV2YWw9Rn0NCnkgPSByZWFkLmNzdigiLi8xIHdkYXkgZHVyYXRpb24uY3N2IikgJT4lIG11dGF0ZSh3ZGF5ID0gYXMuY2hhcmFjdGVyKHdkYXkpKSAlPiUgYXMudGliYmxlKCkNCg0KeiA9IHJlYWQuY3N2KCIuLzIgd2RheSBkdXJhdGlvbi5jc3YiKSAlPiUgbXV0YXRlKHdkYXkgPSBhcy5jaGFyYWN0ZXIod2RheSkpICU+JSBhcy50aWJibGUoKQ0KDQppbm5lcl9qb2luKHksIHosIGJ5ID0gIndkYXkiKSAlPiUgbXV0YXRlKGQubWVkID0gKGQudG90YWwueCArIGQudG90YWwueSkvKG4ueCArIG4ueSkpICU+JSBzZWxlY3Qod2RheSwgZC5tZWQpICU+JSB3cml0ZS5jc3YoLiwgIndkYXkgZHVyYXRpb24uY3N2IikNCg0KYmluZF9yb3dzKHksIHopICU+JSBzZWxlY3QoLVgpICU+JSB3cml0ZS5jc3YoIi4vZGF5IGR1cmF0aW9uLmNzdiIpDQoNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQpyZWFkLmNzdigiaG91cmx5IGR1cmF0aW9uLmNzdiIpICU+JQ0KcGxvdF9seSguLCB4ID0gfiBob3VyLCB5ID0gfiBkLm1lZCwgdHlwZSA9InNjYXR0ZXIiLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLGxpbmUgPSBsaXN0KGNvbG9yPSIjMkU4NkMxIiwgd2lkdGggPSA0KSwgbWFya2VyID0gbGlzdChzaXplID0gOCwgY29sb3IgPSAncmdiYSgyNTUsIDE4MiwgMTkzLCAuOSknLCBsaW5lID0gbGlzdChjb2xvciA9ICdyZ2JhKDE1MiwgMCwgMCwgLjgpJywgd2lkdGggPSAyLHNpbXBseWZ5ID0gRikpLCB0ZXh0ID0gfiBwYXN0ZSgiSG91cjogIiwgaG91ciwgJzxicj5BdmVyYWdlIGR1cmF0aW9uOicsIHJvdW5kKGQubWVkLDIpKSwgbWFpbj0iYXZlcmFnZSBkdXJhdGlvbiB2cyBob3VyIikgJT4lDQogIGxheW91dCh0aXRsZSA9ICdIb3VybHkgTWVkaWFuIFRyaXAgRHVyYXRpb24obWluKScsIHlheGlzID0gbGlzdCh0aXRsZSA9ICdEdXJhdGlvbicsIHplcm9saW5lID0gRkFMU0UpLA0KICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gJ0hvdXInLHplcm9saW5lID0gRkFMU0UpKQ0KYGBgDQoNCg0KYGBge3J9DQpsdmwgPSBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpDQoNCnJlYWQuY3N2KCJ3ZGF5IGR1cmF0aW9uLmNzdiIpICU+JSBtdXRhdGUod2RheSA9IG9yZGVyZWQod2RheSwgbGV2ZWxzID0gbHZsKSkgJT4lIGFycmFuZ2Uod2RheSkgJT4lDQogIHBsb3RfbHkoLiwgeCA9IH4gd2RheSwgeSA9IH4gZC5tZWQsIHR5cGUgPSJzY2F0dGVyIiwgbW9kZSA9ICdsaW5lcyttYXJrZXJzJywgbGluZSA9IGxpc3Qod2lkdGg9NCxzaW1wbHlmeSA9IEYsIGNvbG9yPSdyZ2IoMTE0LCAxODYsIDU5KScpLCBtYXJrZXIgPSBsaXN0KHNpemUgPSA4LCBjb2xvciA9ICcjOEU0NEFEJywgbGluZSA9IGxpc3QoY29sb3IgPSAnIzM0OThEQicsIHdpZHRoID0gMikpLCB0ZXh0ID0gfnBhc3RlKCJXZWVrZGF5OiAiLCB3ZGF5LCAnPGJyPk1lZGlhbiBkdXJhdGlvbjonLCByb3VuZChkLm1lZCwyKSkpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAnV2Vla2RheSBNZWRpYW4gVHJpcCBEdXJhdGlvbihtaW4pIGZvciBGSFYgdHlwZXMnLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0R1cmF0aW9uJyx6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICdXZWVrZGF5Jyx6ZXJvbGluZSA9IEZBTFNFKSkNCmBgYA0KDQoNCg==